#define __STDC_LIMIT_MACROS
#include <stdint.h>
#include <cstdlib>
#include <cstring>

#include "../include/QLeafNode.h"
#include "../include/QInternalNode.h"
#include "../include/QNode.h"
#include "../include/Iterator.h"
#include "../include/util.h"

QLeafNode::QLeafNode(QNode* parentNode, uint32_t childID, QNode* nextRightNode, QNode* nextLeftNode) {
    this->parent = (QInternalNode*) parentNode;
    this->nextRightLeafNode = (QLeafNode*) nextRightNode;
    this->nextLeftLeafNode = (QLeafNode*) nextRightNode;
    this->childID = childID;
}

QLeafNode::QLeafNode(QNode* parentNode, uint32_t childID) {
    this->parent = (QInternalNode*) parentNode;
    this->nextRightLeafNode = NULL;
    this->nextLeftLeafNode = NULL;
    this->childID = childID;
    this->bucket.record_count = 0;
}

void QLeafNode::destruct(bool flag) {
    if (flag)
        this->~QLeafNode();
    else {
        this->bucket.records.clear();
        delete this->nextRightLeafNode;
        try {
            //     delete this->parent;
        } catch (...) {
            //                      std::cout<<"Error in destructing parent pointer"<<std::endl;
        }
    }
}

QLeafNode::~QLeafNode(void) {
    while (!this->bucket.records.empty()) {
        Record* r = this->bucket.records.back();
        this->bucket.records.pop_back();
        for (int i = 0; i < r->key.attribute_count; i++)
            delete r->key.value[i];
        delete r->key.value;
        free(r->payload.data);
        // TODO correct deleting void* is undefined
        r->payload.data = NULL;
        delete r;
    }
}

ErrorCode QLeafNode::insertBucketRecord(Record *r) {
    //TODO:: For optimization we can compare the record key with the begin of bucket records key and last as well
    //and then determine which distance is minimum depending on the difference in which dimension number we got difference
    //and then traverse either fwd or backward - to do smart comparision

    // This is code for sorting of records. Mohammad in favor of this
    //IDEA: If bucket is accessed once for search, sort it then, and raise the flag sorted= true, so in future if
    // you insert, do use the above algo, or insert at the end and raise flag sorted=false again.
    // this will help in future search, that once the records are sorted as well as splitter is median

    try {
        //Record* copyRecord = new Record();
        //CopyRecord(copyRecord, r);
        this->bucket.records.push_back(r);
        this->bucket.record_count++;
        return kOk;
    } catch (std::bad_alloc &e) {
        return kErrorOutOfMemory;
    } catch (ErrorCode &e) {
        return e;
    } catch (...) {
        return kErrorGenericFailure;
    }
    return kOk;
}

/**
 * Without Ignore Payload, both for Match Duplicate and without Match Duplicate -> Case 0 and Case1
 * 
 * @param r
 * @param new_payload
 * @param flagMatchDuplicates
 * @return 
 */
ErrorCode QLeafNode::updateBucketRecord(Record *r, Block *new_payload,
        bool flagMatchDuplicates) {
    try {
        bool flagUpdatedSuccessfully = false;
        std::list< Record*>::iterator it = this->bucket.records.begin();
        for (; it != this->bucket.records.end();
                it++) {
            if (recordCmpQtree(*r, *(*it)) == true) {
                CopyPayload((*it)->payload, *new_payload);
                flagUpdatedSuccessfully = true;

                if (!flagMatchDuplicates)
                    break;
            }
        }
        if (flagUpdatedSuccessfully)
            return kOk;
        return kErrorNotFound;
    } catch (std::bad_alloc &e) {
        return kErrorOutOfMemory;
    } catch (ErrorCode &e) {
        return e;
    } catch (...) {
        return kErrorGenericFailure;
    }
    return kOk;
}

/**
 * With Ignore PayLoad , both for MatchDuplicate and without Match Duplicate, case 2 and case 3
 * 
 * @param k
 * @param new_payload
 * @param flagMatchDuplicates
 * @return 
 */
ErrorCode QLeafNode::updateBucketRecord(Key *k, Block *new_payload,
        bool flagMatchDuplicates) {
    try {
        bool flagUpdatedSuccessfully = false;
        std::list< Record*>::iterator it = this->bucket.records.begin();
        for (; it != this->bucket.records.end(); it++) {
            if (keycmp(*k, (*it)->key) == 0) {
                CopyPayload((*it)->payload, *new_payload);
                flagUpdatedSuccessfully = true;
                if (!flagMatchDuplicates)
                    break;
            }
        }
        if (flagUpdatedSuccessfully)
            return kOk;
        return kErrorNotFound;
    } catch (std::bad_alloc &e) {
        return kErrorOutOfMemory;
    } catch (ErrorCode &e) {
        return e;
    } catch (...) {
        return kErrorGenericFailure;
    }
    return kOk;
}

ErrorCode QLeafNode::deleteBucketRecord(Record *r, bool flagMatchDuplicates) {
    try {
        bool flagDeletedSuccessfully = false;
        std::list< Record*>::iterator it = this->bucket.records.begin();
        for (; it != this->bucket.records.end();) {
            if (recordCmpQtree(*r, *(*it)) == true) {
                it = this->bucket.records.erase(it);
                this->bucket.record_count--;
                flagDeletedSuccessfully = true;
                if (!flagMatchDuplicates)
                    break;
            } else
                ++it;
        }
        if (flagDeletedSuccessfully)
            return kOk;
        return kErrorNotFound;
    } catch (std::bad_alloc &e) {
        return kErrorOutOfMemory;
    } catch (ErrorCode &e) {
        return e;
    } catch (...) {
        return kErrorGenericFailure;
    }
    //TODO: delete record itself as well ; till now we have only deleted pointer from Index,
    // do it after commit or if you are inserting the same record, then do it here.
    return kOk;
}

ErrorCode QLeafNode::deleteBucketRecord(Key *k, bool flagMatchDuplicates) {
    try {
        bool flagDeletedSuccessfully = false;
        std::list< Record*>::iterator it = this->bucket.records.begin();

        for (; it != this->bucket.records.end();) {
            if (keycmp(*k, (*it)->key) == 0) {
                it = this->bucket.records.erase(it);
                this->bucket.record_count--;
                flagDeletedSuccessfully = true;
                if (!flagMatchDuplicates)
                    break;
            } else
                ++it;
        }
        if (flagDeletedSuccessfully) {
            /*if(this->bucket.record_count==0)  TODO:: Check if bucketSize has become 0 remove this leaf
             and free all the pointers and if the parentNode of this leaf had this only as child node
             then after removing this leaf, we will convert back the parent Node to leafNode
             {
             this->parent->childs[this->childID]=NULL;
             }*/
            return kOk;
        } else
            return kErrorNotFound;
    } catch (std::bad_alloc &e) {
        return kErrorOutOfMemory;
    } catch (ErrorCode &e) {
        return e;
    } catch (...) {
        return kErrorGenericFailure;
    }
    //TODO: delete record itself as well ; till now we have only deleted pointer from Index,
    //do it after commit or if you are inserting the same record, then do it here.
    return kOk;
}

ErrorCode QLeafNode::fakeModifyRecord(Record *r, Iterator **itRecords, bool flagMatchDuplicates) {
    try {
        bool flagModifiedSuccessfully = false;
        std::list< Record*>::iterator it = this->bucket.records.begin();
        for (; it != this->bucket.records.end();
                it++) {
            if (recordCmpQtree(*r, *(*it)) == true) {
                (*itRecords)->insert(*it); // ToDo:: Uncomment it after including iterator in the code
                flagModifiedSuccessfully = true;
                if (!flagMatchDuplicates)
                    break;
            }
        }
        if (flagModifiedSuccessfully)
            return kOk;
        return kErrorNotFound;
    } catch (std::bad_alloc &e) {
        return kErrorOutOfMemory;
    } catch (ErrorCode &e) {
        return e;
    } catch (...) {
        return kErrorGenericFailure;
    }
    return kOk;
}

/**
 * With Ignore PayLoad , both for MatchDuplicate and without Match Duplicate, case 2 and case 3
 * 
 * @param k
 * @param it
 * @param flagMatchDuplicates
 * @return 
 */
ErrorCode QLeafNode::fakeModifyRecord(Key *k, Iterator **it, bool flagMatchDuplicates) {
    try {
        bool flagModifiedSuccessfully = false;
        std::list< Record*>::iterator it = this->bucket.records.begin();
        for (; it != this->bucket.records.end();
                it++) {
            if (keycmp(*k, (*it)->key) == 0) {

                //	(*itRecords)->insert(*it);  ToDo:: Uncomment it after including iterator in the code
                flagModifiedSuccessfully = true;

                if (!flagMatchDuplicates)
                    break;
            }
        }
        if (flagModifiedSuccessfully)
            return kOk;
        return kErrorNotFound;
    } catch (std::bad_alloc &e) {
        return kErrorOutOfMemory;
    } catch (ErrorCode &e) {
        return e;
    } catch (...) {
        return kErrorGenericFailure;
    }
    return kOk;
}

ErrorCode QLeafNode::findRange(Key *min, Key *max, Iterator** iteratorList) {
    try {
        int cmpResult;
        std::list< Record*>::iterator it = this->bucket.records.begin();

        Record * my_record = new Record();
        for (; it != this->bucket.records.end(); it++) {
            cmpResult = keycmp_range((*it)->key, *max, *min);
            if (cmpResult == 0) {
                CopyRecord(my_record, *it);
                (*iteratorList)->insert(my_record);
            }
        }
    } catch (std::bad_alloc &e) {
        throw (kErrorOutOfMemory);
    } catch (ErrorCode &e) {
        throw (e);
    } catch (...) {
        throw (kErrorGenericFailure);
    }

    return kOk;
}

ErrorCode QLeafNode::find(Key *k, Iterator ** iteratorList) {
    try {
        int cmpResult;

        //Modified @ March 10; 10:10PM
        std::list< Record*>::iterator it = this->bucket.records.begin();

        Record * my_record = new Record();
        for (; it != this->bucket.records.end(); it++) {
            cmpResult = keycmp(*k, (*it)->key);

            if (cmpResult == 0) {
                CopyRecord(my_record, *it);
                (*iteratorList)->insert(my_record);
            }
        }
    } catch (std::bad_alloc &e) {
        throw (kErrorOutOfMemory);
    } catch (ErrorCode &e) {
        throw (e);
    } catch (...) {
        throw (kErrorGenericFailure);
    }

    return kOk;
}

ErrorCode QLeafNode::findPartial(Key *k, Iterator** iteratorList) {
    try {
        int cmpResult;
        std::list< Record*>::iterator it = this->bucket.records.begin();

        Record * my_record = new Record();
        for (; it != this->bucket.records.end(); it++) {
            cmpResult = recordsCmp_partial((*it)->key, *k);

            if (cmpResult == 0) {
                CopyRecord(my_record, *it);
                (*iteratorList)->insert(my_record);
            }
        }
    } catch (std::bad_alloc &e) {
        throw (kErrorOutOfMemory);
    } catch (ErrorCode &e) {
        throw (e);
    } catch (...) {
        throw (kErrorGenericFailure);
    }

    return kOk;
}
